//=============================================================================

#include <iomanip>
#include <iterator>

#include "tinyxml.h"

#include "levelmanager.hpp"
#include "filemanager.hpp"
#include "log.hpp"
#include "common.hpp"

/////////////////////////////////////////////
#include "texture.hpp"
#include "staticimage.hpp"
#include "scrollimage.hpp"
#include "foreimage.hpp"
#include "frames.hpp"
#include "glee.h"
#include "global.hpp"
#include "player.hpp"

#include "fmod.hpp"
#include "fmod_errors.h"


// GLobals
extern FMOD::System *fmodSystem;
extern FMOD::Sound *MusicHandle;
extern FMOD::Channel *MusicChannel;

extern Player PJ;

// level variables
//int currentLvl = 1;
std::string levelName;
int levelTileWidth=0, levelTileHeight=0;
int levelPixelWidth=0, levelPixelHeight=0;

//textures
KEG::Texture tileTex;
KEG::StaticImage mainBack;
std::vector< ScrollImage > scroller;
std::vector< ForeImage > foreground;

bool backgroundActive = false;
//bool scrollActive = false;

GLuint dispList;
float dispBoxWidth;
float dispBoxHeight;
int dispGridWidth;
int dispGridHeight;

//[HACK]
//[WORK] more flexible system for tile size and display box size
#define DISPLAY_TILE_WIDTH 10
#define DISPLAY_TILE_HEIGHT 8
#define TILE_SIZE 32

namespace LevelManager
{

int LoadFirstLevel()
{
	return Load(1);
}

int Load( int number )
{
	std::ostringstream sstr;
	sstr << std::setfill('0') << std::setw(3) << number;

	return Load( KEG::Path( LEVEL, sstr.str() ) );
}

// load helper functions
void LoadTileData(TiXmlElement* tileNode)
{
	// get tile data
	KEG::TileTexture tileTexCoord;
	//tileTexCoord.clear();
	//tileTexCoord.push_back(1.1f);
	KEG::LoadTilesAndTex( KEG::Path( FRAME, tileNode->Attribute("frames") ), tileTexCoord, tileTex );

	// create 2D grid to temporarily hold tile data
	int width=0, height=0;
	tileNode->QueryIntAttribute( "width", &width );
	tileNode->QueryIntAttribute( "height", &height );
	// set file variables
	::levelTileWidth = width;
	::levelTileHeight = height;
	::levelPixelWidth = ::levelTileWidth * TILE_SIZE;
	::levelPixelHeight = ::levelTileHeight * TILE_SIZE;
	if( width==0 || height==0 )
	{
		LOG_ERROR( "Tile Data Error: Width X Height" );
	}
	int* tileGrid = new int[width*height];
	// load it in
	std::istringstream iss( tileNode->GetText() );
	//[DEBUG]
	//LOG_RAW() << iss.str() << std::endl;
	//std::ostream& os = LOG_RAW();
	{
		int index = 0;
		int x,y,z;
		int fill;
		while( index < width*height)
		{
			iss >> x >> y >> z;
			
			fill = y*tileTexCoord.Width + x;
			if( y==-1 || x==-1 )
				fill = -1;

			for( int i=0; i < z; i++)
			{
				tileGrid[index + i] = fill;
			}
			index += z;

			if( !iss.good() )
				break;
		}
		if( width*height != index )
		{
			LOG_ERROR( "Insufficient Tile Data." );
			LOG_RAW() << index << ' '<< width*height << std::endl;
		}
	}
	//spew it
	//[DEBUG]
	//std::ostream& os = LOG_RAW() << std::endl;
	//for( int i = 0; i < height; i++)
	//{
	//	for( int j = 0; j< width; j++)
	//	{
	//		os << tileGrid[i*width+j] << " ";
	//	}
	//	os << std::endl;
	//}

	// load up the display lists
	int dispX = width/DISPLAY_TILE_WIDTH + (width%DISPLAY_TILE_WIDTH?1:0);
	int dispY = height/DISPLAY_TILE_HEIGHT + (height%DISPLAY_TILE_HEIGHT?1:0);
	int numOfLists = dispY * dispX;

	dispBoxWidth = DISPLAY_TILE_WIDTH*TILE_SIZE;
	dispBoxHeight = DISPLAY_TILE_HEIGHT*TILE_SIZE;
	dispGridWidth = dispX;
	dispGridHeight = dispY;
		
	// create
	dispList = glGenLists(numOfLists);

	// loop through each "display list square"
	for( int y = 0; y < dispY; ++y)
	{
		for( int x = 0; x < dispX; ++x)
		{
			// create the list
			::glNewList( dispList + y*dispX + x, GL_COMPILE );
			glBegin( GL_QUADS );
			//keep track of the screen position with Sx, Sy
			//for( float Sy = y*DISPLAY_TILE_HEIGHT*TILE_SIZE; Sy < (y+1)*DISPLAY_TILE_HEIGHT*TILE_SIZE;
			//	Sy += TILE_SIZE )
			//{
			//	for( float Sx = x*DISPLAY_TILE_WIDTH*TILE_SIZE; Sx < (x+1)*DISPLAY_TILE_WIDTH*TILE_SIZE;
			//		Sx += TILE_SIZE )
			//	{
			for( int Sy = 0; Sy < DISPLAY_TILE_HEIGHT; ++Sy )
			{
				for( int Sx = 0; Sx < DISPLAY_TILE_WIDTH; ++Sx )
				{
					int tileX = x*DISPLAY_TILE_WIDTH+Sx;
					int tileY = y*DISPLAY_TILE_HEIGHT+Sy;
					int tileIndex = tileY*width + tileX;

					if( tileIndex >= width*height )
						continue;

					if( tileGrid[ tileIndex ] != -1 )
					{
						glTexCoord2f(tileTexCoord.Coord[tileGrid[ tileIndex ]*4],tileTexCoord.Coord[tileGrid[ tileIndex ]*4+1]);
						glVertex2f(tileX*TILE_SIZE,tileY*TILE_SIZE);
						glTexCoord2f(tileTexCoord.Coord[tileGrid[ tileIndex ]*4+2],tileTexCoord.Coord[tileGrid[ tileIndex ]*4+1]);
						glVertex2f((tileX+1)*TILE_SIZE,tileY*TILE_SIZE);
						glTexCoord2f(tileTexCoord.Coord[tileGrid[ tileIndex ]*4+2],tileTexCoord.Coord[tileGrid[ tileIndex ]*4+3]);
						glVertex2f((tileX+1)*TILE_SIZE,(tileY+1)*TILE_SIZE);
						glTexCoord2f(tileTexCoord.Coord[tileGrid[ tileIndex ]*4],tileTexCoord.Coord[tileGrid[ tileIndex ]*4+3]);
						glVertex2f(tileX*TILE_SIZE,(tileY+1)*TILE_SIZE);
					}
				}
			}
			glEnd();
			::glEndList();
		}
	}
}

int Load( const std::string& name )
{
	LOG_INFO( "Loading New Level File" );
	LOG_RAW() << "Level Filename: " << name << std::endl;

	// =

	TiXmlDocument *xmlDoc = new TiXmlDocument(name);
	if (!xmlDoc->LoadFile())
	{
		// Fatal error, cannot load
		LOG_ERROR( "Level file not found!" );
		//LOG_RAW() << filename << std::endl;
		return 0;
	}

	// Grab the outer element
	TiXmlElement *topNode = xmlDoc->FirstChildElement("level");
	if( !topNode )
	{
		LOG_ERROR( "Outer XML Broken in Level File" );
		return 0;
	}

	std::string musicFile = topNode->Attribute("music");
	levelName = topNode->Attribute("name");

	// Load music file
	LOG_RAW() << "Loading Music File " << KEG::Path( MUSIC, musicFile ) << std::endl;

	if( FMOD_OK != fmodSystem->createSound( KEG::Path( MUSIC, musicFile ).c_str(),
		FMOD_LOOP_NORMAL | FMOD_2D | FMOD_CREATESTREAM, 0, &MusicHandle ) )
	{
		LOG_ERROR( "Music File Failed." );
	}
	else
	{
		// [TOGGLE] level music
		fmodSystem->playSound(FMOD_CHANNEL_FREE, MusicHandle, false, &MusicChannel);
	}

	//load player
	TiXmlElement* playerNode = 0;
	float playerX=0, playerY=0;
	if( playerNode = topNode->FirstChildElement( "player" ) )
	{
		std::stringstream fmt;
		fmt << playerNode->Attribute("x") << ' ' << playerNode->Attribute("y");
		fmt >> playerX >> playerY;
		if( fmt.bad() )
			LOG_ERROR("Player Data is BAD!");
		else
			PJ.StartPosition(playerX,playerY);
	} else {
		LOG_ERROR("No Player Data in Level XML File.");
	}

	// load tile data
	TiXmlElement* tileNode = 0;
	if( (tileNode = topNode->FirstChildElement("tiles")) == NULL )
	{
		LOG_ERROR( "No Tile Data Found." );
		return 0;
	}
	
	LoadTileData( tileNode );

	// load background
	//TiXmlElement* backNode = 0;
	for( TiXmlElement* backNode = topNode->FirstChildElement( "background" );
			backNode;
			backNode = backNode->NextSiblingElement( "background" ) )
	{
		if( !backNode->Attribute("image") )
		{
			LOG_ERROR("Broken Background Image: No Image File Name.");
			continue;
		}
		if( strcmp( "static", backNode->Attribute("type") ) == 0 )
		{
			if( mainBack.LoadFullImage( KEG::Path( IMAGE, backNode->Attribute("image") ) ) )
			{
				backgroundActive = true;
			}
			LOG_INFO( "Static background loaded." );
		} else if ( strcmp( "scroll", backNode->Attribute("type") ) == 0 ) {
			scroller.push_back( ScrollImage() );
			if( scroller.back().Load( KEG::Path( IMAGE, backNode->Attribute("image") ) ) )
			{
				float startX=0, startY=0, f=0;
				std::stringstream ss;
				// [WORK] need better use of ss
				if( backNode->Attribute( "startx" ) )
				{
					ss << backNode->Attribute( "startx" );
					ss >> startX;
					ss.ignore();
					ss.flush();
					ss.clear();
				}
				if( backNode->Attribute( "starty" ) )
				{
					if( strcmp( "bottom", backNode->Attribute( "starty" ) ) == 0 )
					{
						startY = Global::Game::Height - scroller.back().Height();
					} else {
						ss << backNode->Attribute( "starty" );
						ss >> startY;
						ss.ignore();
						ss.flush();
						ss.clear();
					}
				}
				if( backNode->Attribute( "scrollx" ) )
				{
					ss << backNode->Attribute( "scrollx" );
					ss >> f;
					scroller.back().XScroll( f );
					f=0;
					ss.ignore();
					ss.flush();
					ss.clear();
				}
				if( backNode->Attribute( "scrolly" ) )
				{
					ss << backNode->Attribute( "scrolly" );
					ss >> f;
					scroller.back().YScroll( f );
					f=0;
					ss.ignore();
					ss.flush();
					ss.clear();
				}
				if( backNode->Attribute( "top" ) )
				{
					if( strcmp( "starty", backNode->Attribute( "top" ) ) == 0 )
					{
						scroller.back().TopLimit( startY );
					} else {
						ss << backNode->Attribute( "top" );
						ss >> f;
						scroller.back().TopLimit( f );
						f=0;
						ss.ignore();
						ss.flush();
						ss.clear();
					}
				}
				if( backNode->Attribute( "bottom" ) )
				{
					ss << backNode->Attribute( "bottom" );
					ss >> f;
					scroller.back().BottomLimit( f );
					f=0;
					ss.ignore();
					ss.flush();
					ss.clear();
				}
				if( backNode->Attribute( "repeat" ) )
				{
					if(strcmp( "x", backNode->Attribute( "repeat" ) ) == 0 )
						scroller.back().RepeatX(true);
					else if(strcmp( "y", backNode->Attribute( "repeat" ) ) == 0 )
						scroller.back().RepeatY(true);
					else if(strcmp( "xy", backNode->Attribute( "repeat" ) ) == 0 )
					{
						scroller.back().RepeatX(true);
						scroller.back().RepeatY(true);
					}
				}
				// 32 is 1/2 player width or height
				scroller.back().ScreenPosition( startX, startY );
				scroller.back().BasePosition( playerX+32 -Global::Game::Width/2,
					playerY+32 -Global::Game::Height/2 );
				//LOG_RAW() << ss.str() << " Final set (x,y) at (" << startx << ", " << starty << ")" << std::endl;
				if( backNode->Attribute( "stretch" ) && strcmp( "yes", backNode->Attribute( "stretch" ) ) == 0 )
					scroller.back().Stretch( Global::Game::Width, Global::Game::Height );
				//scrollActive = true;
				LOG_INFO( "Scrolling background loaded." );
			}
		}
	}

	// load foreground
	for( TiXmlElement* foreNode = topNode->FirstChildElement( "foreground" );
			foreNode;
			foreNode = foreNode->NextSiblingElement( "foreground" ) )
	{
		if( !foreNode->Attribute("image") )
		{
			LOG_ERROR("Broken Foreground Image: No Image File Name.");
			continue;
		}
		foreground.push_back( ForeImage() );
		if( foreground.back().Load( KEG::Path( IMAGE, foreNode->Attribute("image") ) ) )
		{
			LOG_INFO( "Foreground object loaded." );
		}
		float startX=0, startY=0, f=0;
		std::stringstream ss;
		// [WORK] need better use of ss
		if( foreNode->Attribute( "startx" ) )
		{
			ss << foreNode->Attribute( "startx" );
			ss >> startX;
			ss.ignore();
			ss.flush();
			ss.clear();
		}
		if( foreNode->Attribute( "starty" ) )
		{
			if( strcmp( "bottom", foreNode->Attribute( "starty" ) ) == 0 )
			{
				startY = levelPixelHeight-foreground.back().Height();
			} else {
				ss << foreNode->Attribute( "starty" );
				ss >> startY;
				ss.ignore();
				ss.flush();
				ss.clear();
			}
		}
		foreground.back().RelPosition(startX, startY);
	}

	// load scrolling images
	//TiXmlElement* scrollNode = 0;

	delete xmlDoc;

	return 1;
}

void Unload()
{
	glDeleteLists(dispList, dispGridWidth*dispGridHeight);

	MusicChannel->stop();
	MusicHandle->release();
}

void Render( float x, float y )
{
	glColor4f(1,1,1,1);
	//glBindTexture( GL_TEXTURE_RECTANGLE_ARB, mainBack.Tex );
	//glBegin( GL_QUADS );
	//glTexCoord2f(0.0f, 0.0f); glVertex2f(0.0f, 0.0f);
	//glTexCoord2f(mainBack.Width, 0.0f); glVertex2f(640.0f, 0.0f);
	//glTexCoord2f(mainBack.Width, mainBack.Height); glVertex2f(640.0f, 480.0f);
	//glTexCoord2f(0.0f, mainBack.Height); glVertex2f(0.0f, 480.0f);
	//glEnd();
	glBindTexture( GL_TEXTURE_RECTANGLE_ARB, tileTex.Tex );
	glPushMatrix();
	glTranslatef(-x,-y,0);

	//calc first display list box
	int startY = (y/dispBoxHeight);//*dispGridWidth;
	int startX = (x/dispBoxWidth);
	//int startBox2 = startBox + dispGridWidth;
	//int startBox3 = startBox2 + dispGridWidth;

	//int super = 999;
	// calc starting screen position
	// [DEBUG] just for map viewing - real game restricts camera so display list not out of bounds
	if( startY < 0 )
		startY = 0;
	if( startX < 0 )
		startX = 0;

	for( int iy = 0;
		startY + iy < dispGridHeight && iy < (Global::Game::Height/dispBoxHeight + 1);
		++iy )
	{
		for( int ix = 0;
			startX + ix < dispGridWidth && ix < (Global::Game::Width/dispBoxWidth + 1);
			++ix)
		{
			//assert( dispList + (startY + iy)*dispGridWidth + (startX + ix) > dispList &&
					//dispList + (startY + iy)*dispGridWidth + (startX + ix) < dispList + dispGridWidth*dispGridHeight);
			glCallList( dispList + (startY + iy)*dispGridWidth + (startX + ix) );
			//super = (startY + iy)*dispGridWidth + (startX + ix);
			assert( glGetError() == GL_NO_ERROR );
		}
	}

	// temp
	// hardcode
	//glCallList(dispList + startBox);
	//glCallList(dispList + startBox + 1);

	//glCallList(dispList + startBox2);
	//glCallList(dispList + startBox2 + 1);

	//if( startBox3 < dispGridWidth*dispGridHeight )
	//{
	//	glCallList(dispList + startBox3);
	//	glCallList(dispList + startBox3 + 1);
	//}

	//glCallList(dispList);
	glPopMatrix();

	//if( scrollActive )
	// render front to back with alpha test and depth buffer with depth write
	for( std::vector< ScrollImage >::const_iterator itr = scroller.begin(); itr != scroller.end(); ++itr )
		itr->Render( x, y );
	if( backgroundActive )
		mainBack.Render( 0.0f, 0.0f );
}

void RenderForeground( float x, float y)
{
	glDisable(GL_DEPTH_TEST);
	glDisable(GL_ALPHA_TEST);
	glEnable(GL_BLEND);

	for( std::vector< ForeImage >::const_iterator itr = foreground.begin(); itr != foreground.end(); ++itr )
		itr->Render( x, y );

	glDisable(GL_BLEND);
	glEnable(GL_ALPHA_TEST);
	glEnable(GL_DEPTH_TEST);
}

void PlayMusic()
{
	MusicChannel->setPaused(0);
}

void PauseMusic()
{
	MusicChannel->setPaused(1);
}

} // namespace
